
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This file contains the implementation for all the Function standard
// library stuff that can be implemented completely in javascript

// Ecma 15.3.1.1 Function(p1, p2, ..., pn, body)
// When Function is called as a function rather than as a constructor,
// it creates and initializes a new Function object. Thus the
// function call Function(...) is equivalent to the object creation expression
// new Function(...) with the same arguments.
// In other words, there's only one implementation for Function.

// Ecma 15.3.2.1 new Function(p1, p2, ..., pn, body)
// The last argument specifies the body of a function; any preceding arguments
// specify formal paramters
Esiply.set_attributes(this, "Function", Esiply.DONT_ENUM);
Function = function(p0)
{
    var body = "";
    var params = "";

    if (arguments.length > 0)
    {
        body = arguments[arguments.length-1];
    }
    if (arguments.length > 1)
    {
        params = arguments[0];
    }
    for (var i = 1; i < arguments.length-1; i++)
    {
        params += ", " + arguments[i];
    }

    // TODO: We're supposed to pass in a scope chain consisting of
    // the global object as the Scope parameter. Is this right?
    // (I think it isn't)
    return eval("(function(" + params + "){" + body + "});");
};

// Ecma 15.3.3.1 Function.prototype
// The initial value of Function.prototype is the Function prototype object.
// This property has attributes {DontEnum, DontDelete, ReadOnly}
Function.prototype = Esiply.function_prototype;
Esiply.set_attributes(Function, "prototype",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.3.4.1 Function.prototype.constructor
// The initial value of Function.prototype.constructor is the built-in Function
// constructor
Esiply.set_attributes(Function.prototype, "constructor", Esiply.DONT_ENUM);
Function.prototype.constructor = Function;

// Ecma 15.3.4.3 Function.prototype.apply (thisArg, argArray)
// The apply method takes two arguments, thisArg and argArray, and performs
// a function call using the [[call]] property of the object. If the object
// does not have a [[call]] property, a TypeError exception is thrown.
// TODO: the way this function is implemented is totally bogus. We are 
// adding properties to other peoples objects, which is not right. I think
// we need internal support for this.
Esiply.set_attributes(Function.prototype, "apply", Esiply.DONT_ENUM);
Function.prototype.apply = function(thisArg, argArray)
{
    // TODO: If thisArg is null or undefined, the called function is passed the 
    // global object as the this value. Otherwise the called function is
    // passed ToObject(thisArg) as the this value.

    // If argArray is null or undefined, the called function is passed no
    // arguments. Otherwise, if argArray is neither an array
    // TODO: nor an arguments object (how do I know if its an arguments object?
    // a TypeError exception is thrown.
    if (typeof argArray == "undefined" || typeof argArray == "null")
    {
        argArray = [];
    }

    // To call the function with thisArg as this, we need to add the function
    // as a property to thisArg. But it would be bad to overwrite someone's
    // existing property, so first find a property that isn't there. We'll
    // set this function to that property, call it, and delete it.
    thisArg = Object(thisArg);
    var freeprop = "$f0";
    for (var i = 1; thisArg.hasOwnProperty(freeprop); i++)
    {
        freeprop = "$f" + i;
    }

    thisArg[freeprop] = this;
    
    // To call the function we'll create a function call string using eval.
    // It looks like: thisArg[freeprop](argArray[0], argArray[1], etc...)
    var callstr = "thisArg[freeprop](";
    for (var i = 0; i < argArray.length; i++)
    {
        callstr += "argArray[" + i + "],";
    }

    // Kill the last comma
    callstr = callstr.substring(0, callstr.length-1);

    callstr += ");";
    var res = eval(callstr);

    // Now remove the function property
    delete thisArg[freeprop];

    return res;
};

// Ecma 15.3.4.4 Function.prototype.call(thisArg[,arg1,arg2,...]])
// This is the same as the apply method, only the arguments are passed in
// as a varags instead of an array of args.
Esiply.set_attributes(Function.prototype, "call", Esiply.DONT_ENUM);
Function.prototype.call = function(thisArg)
{
    // Turn the arguments into an array. And just use apply
    arguments.slice = Array.prototype.slice;
    return this.apply(thisArg, arguments.slice(1));
};
    
